package BeatBox;

import java.io.*;
import java.awt.*;
import javax.swing.*;
import javax.sound.midi.*;
import java.util.*;
import java.awt.event.*;

public class BeatBox
{
    JFrame theFrame;//图形界面的结构
        JPanel background;
            Box nameBox;//background左部：乐器名
            JPanel mainPanel;//background中部：节拍数及256个复选框
            JPanel rightPanel;//background右部
                Box buttonBox;//一组按钮
                JPanel incomingPanel;//接收区域
                JPanel outgoingpPanel;//发送区域
                JPanel IPaddresspPanel;//IP地址输入区
                
    ArrayList<JCheckBox> checkboxList;
    Sequencer sequencer;
    Sequence sequence;
    Track track;
    SimpleChatClient client;
    
    String[] instrumentNames = {"Beat count","Bass Drum", "Closed Hi-Hat", "Open Hi-Hat", "Acoustic Snare", "Crash Cymbal",
    "Hand Clap", "High Tom", "Hi Bongo", "Maracas", "Whistle", "Low Conga", "Cowbell", "Vibraslap",
    "Low-mid Tom","High Agogo", "open Hi Conga" };
    int[] instruments = { 35, 42, 46, 38, 49, 39, 50, 60, 70, 72, 64, 56, 58, 47, 67, 63 };
    
    public static void main(String[] args) 
    {
        new BeatBox().buildGUI();
    }
    
    public void buildGUI() 
    {
        theFrame = new JFrame("Cyber BeatBox");
        theFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        BorderLayout layout = new BorderLayout();
        background = new JPanel(layout);
        background.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));//设置边界宽度

        nameBox = new Box(BoxLayout.Y_AXIS);//添加乐器名字
        for (int i = 0; i < 17; i++) 
        {
            nameBox.add(new Label(instrumentNames[i]));
        }
        
        checkboxList = new ArrayList<JCheckBox>();
        GridLayout grid = new GridLayout(17, 16);
        /*GridLayout 类是一个布局处理器，它以矩形网格形式对容器的组件进行布置。
        容器被分成大小相等的矩形，一个矩形中放置一个组件。*/
        grid.setVgap(1);//将组件之间的垂直间距设置为指定值。
        grid.setHgap(2);//将组件之间的水平间距设置为指定值。
        mainPanel = new JPanel(grid);
        for(int i = 0;i<16;i++) 
        {
            mainPanel.add(new Label(""+(i+1)));
        }
        for (int i = 0; i < 256; i++)//添加256个复选框 
        {
            JCheckBox c = new JCheckBox();//复选框的实现，复选框是一个可以被选定和取消选定的项，它将其状态显示给用户。
            c.setSelected(false);
            checkboxList.add(c);
            mainPanel.add(c);
        }
        

        JButton start = new JButton("Start");//添加按钮
        start.addActionListener(new MyStartListener());

        JButton stop = new JButton("Stop");
        stop.addActionListener(new MyStopListener());
        
        JButton upTempo = new JButton("Tempo Up");
        upTempo.addActionListener(new MyUpTempoListener());
        
        JButton downTempo = new JButton("Tempo Down");
        downTempo.addActionListener(new MyDownTempolistener());
        
        JButton save = new JButton("Save rhythm");
        save.addActionListener(new MySaveListener());

        JButton laod = new JButton("Laod rhythm");
        laod.addActionListener(new MyLaodListener());
        
        JButton share= new JButton("Share");
		share.addActionListener(new MyShareListener());

        JButton listen = new JButton("Listen");
        listen.addActionListener(new MyListenListener());
        
        buttonBox = new Box(BoxLayout.Y_AXIS);
        buttonBox.add(start);
        buttonBox.add(stop);
        buttonBox.add(upTempo);
        buttonBox.add(downTempo);
        buttonBox.add(save);
        buttonBox.add(laod);
        buttonBox.add(share);
        buttonBox.add(listen);
        
        client =new SimpleChatClient();//加入一个聊天客户端
        client.buildGui();
        incomingPanel=client.incomingPanel;
        outgoingpPanel=client.outgoingpPanel;
        IPaddresspPanel=client.IPaddresspPanel;
        client.incoming.setColumns(25);
        client.incoming.setRows(15);

        rightPanel = new JPanel();
        rightPanel.setLayout(new BoxLayout(rightPanel,BoxLayout.Y_AXIS));
        rightPanel.add(BorderLayout.NORTH, buttonBox);//Box被添加到background上，background是一个使用Borderlayout布局的JPanel
        rightPanel.add(BorderLayout.CENTER, incomingPanel);
        rightPanel.add(BorderLayout.SOUTH, outgoingpPanel);
        rightPanel.add(BorderLayout.SOUTH, IPaddresspPanel);
        
        
        background.add(BorderLayout.WEST, nameBox);
        background.add(BorderLayout.CENTER, mainPanel);//background添加了mainPanel
        background.add(BorderLayout.EAST,rightPanel);

        setUpMidi();
        
        theFrame.getContentPane().add(background);//给theFrame添加background
        theFrame.setBounds(256,144,1024,600);
        /*移动组件并调整其大小。由 x 和 y 指定左上角的新位置，由 width 和 height 指定新的大小。
        与真实的像素大小之比大概为0.8，有待考证*/
        // theFrame.pack();//调整此窗口的大小，以适合其子组件的首选大小和布局。
        theFrame.setVisible(true);
        client.IPaddress.selectAll();
		client.IPaddress.requestFocus();
        // client.outgoing.requestFocus();
    } // 关闭方法
    
    public void setUpMidi() 
    {
        try 
        {
            sequencer = MidiSystem.getSequencer();
            sequencer.open();
            sequence = new Sequence(Sequence.PPQ, 4);
            track = sequence.createTrack();
            sequencer.setTempoInBPM(120);//设置速度，以每分钟的拍数为单位。
        } 
        catch (Exception e) 
        {
            e.printStackTrace();
        }
    }

    //设置监视动作
    public class MyStartListener implements ActionListener//开始播放
    {
        public void actionPerformed(ActionEvent a) 
        {
            buildTrackAndstart();
        }
    } // 关闭内部类
    
    public class MyStopListener implements ActionListener//停止播放
    {
        public void actionPerformed(ActionEvent a) 
        {
            sequencer.stop();
        }
    }
    
    public class MyUpTempoListener implements ActionListener//加快节奏
    {
        public void actionPerformed(ActionEvent a) 
        {
            float tempoFactor = sequencer.getTempoFactor();
            sequencer.setTempoFactor((float) (tempoFactor * 1.03));
        }
        
    } // 关闭内部类
    
    public class MyDownTempolistener implements ActionListener//减慢节奏
    {
        public void actionPerformed(ActionEvent ar) 
        {
            float tempoFactor = sequencer.getTempoFactor();
            sequencer.setTempoFactor((float) (tempoFactor * .97));
        }
    }

    public class MySaveListener implements ActionListener//保存节奏
    {
        public void actionPerformed(ActionEvent ar) 
        {
            try
            {
                FileOutputStream fos = new FileOutputStream(new File("D:\\Checkbox.ser"));
                ObjectOutputStream oStream = new ObjectOutputStream(fos);
                oStream.writeObject(checkboxList);
            }
            catch (IOException e)
            {
                e.printStackTrace();
            }
        }
    }

    public class MyLaodListener implements ActionListener//加载节奏
    {
        @SuppressWarnings("unchecked")
        public void actionPerformed(ActionEvent ar)
        {
            try
            {
                ArrayList<JCheckBox> checkboxList1;
                FileInputStream fis = new FileInputStream(new File("D:\\Checkbox.ser"));
                ObjectInputStream ois = new ObjectInputStream(fis);
                checkboxList1 = (ArrayList<JCheckBox>)ois.readObject();
                for(int i=0;i<256;i++)
                {
                    JCheckBox box = (JCheckBox)checkboxList.get(i);
                    box.setSelected((checkboxList1.get(i)).isSelected());
                }
                buildTrackAndstart();
            }
            catch (ClassNotFoundException | IOException e)
            {
                e.printStackTrace();
            } 
        }
    }

	public class MyShareListener implements ActionListener//分享节奏
    {
		public void actionPerformed(ActionEvent ev) {
            boolean[] checkboxState= new boolean[256];
            for(int i=0;i<256;i++)
            {
                JCheckBox box = (JCheckBox)checkboxList.get(i);
                if(box.isSelected())
                {
                    checkboxState[i]=true;
                };
            }
            client.sendRhythm(checkboxState);
		}
	}

    public class MyListenListener implements ActionListener//听取分享
    {
        public void actionPerformed(ActionEvent ar) 
        {
            for(int i=0;i<256;i++)
            {
                JCheckBox box = (JCheckBox)checkboxList.get(i);
                box.setSelected(client.checkboxState[i]);
            }
            buildTrackAndstart(); 
        }
    }

    public void buildTrackAndstart() 
    {
        int[] trackList = null;
        sequence.deleteTrack(track);
        track = sequence.createTrack();
        for (int i = 0; i < 16; i++)//对每一种乐器添加播放信息
        {
            trackList = new int[16];//trackList记录了第i种乐器的播放信息
            int key = instruments[i];
            for (int j = 0; j < 16; j++) 
            {
                JCheckBox jc = (JCheckBox)checkboxList.get(j + (16 * i));
                if (jc.isSelected()) 
                {
                    trackList[j] = key;
                } 
                else
                {
                    trackList[j] = 0;
                }
            }
            makeTracks(trackList);
            track.add(makeEvent(176, 1, 127, 0, 16));//详见P390
        } // 关闭外部循环

        track.add(makeEvent(192, 9, 1, 0, 15));//详见P422

        try 
        {
            sequencer.setSequence(sequence);
            sequencer.setLoopCount(Sequencer.LOOP_CONTINUOUSLY);//设置为无穷的重复次数
            sequencer.setTempoInBPM(120);
        } 
        catch (Exception e) 
        {
            e.printStackTrace();
        }
        sequencer.start();
    } // 关闭buildTrackAndStart方法

    public void makeTracks(int[] list)//创建一个16拍的节奏 
    {
        for (int i = 0; i < 16; i++) 
        {
            int key = list[i];
            if (key != 0) 
            {
                track.add(makeEvent(144, 9, key, 100, i)); 
                track.add(makeEvent(128, 9, key, 100, i + 1));
            }
        }
    }

    public MidiEvent makeEvent(int comd, int chan, int one, int two, int tick) 
    {
        MidiEvent event = null;
        try 
        {
            ShortMessage a = new ShortMessage();
            a.setMessage(comd, chan, one, two);/*类型(144打开;128关闭;192改变乐器信息);
            频道:选择9;乐器:代号储存在instruments数组中;音道:响度*/
            event = new MidiEvent(a, tick);//tick表示节拍
        }
        catch (Exception e) 
        {
            e.printStackTrace();
        }
        return event;
    }
}
